package com.lebang.medicalInquiry.handler;

import com.alibaba.fastjson.JSONObject;
import com.lebang.medicalInquiry.component.RedisComponent;
import com.lebang.medicalInquiry.constant.MessageAckTypeEnum;
import com.lebang.medicalInquiry.constant.MessageTypeEnum;
import com.lebang.medicalInquiry.constant.RedisKeyEnum;
import com.lebang.medicalInquiry.mapper.OffLineNoticeMapper;
import com.lebang.medicalInquiry.model.vo.MessageModel;
import com.lebang.medicalInquiry.service.AsyncService;
import com.lebang.medicalInquiry.utils.TextUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @version V1.0
 * @author: zpc
 * @date:
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class LiveMessageHandler extends SimpleChannelInboundHandler<Object> {

    //所有用户组
    private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    // 主播和观看直播的用户组映射
    public static ConcurrentHashMap<String, ChannelGroup> liveAndUuidChannel = new ConcurrentHashMap<>();
    //观看直播用户和直播间的映射
    public static ConcurrentHashMap<String, String> uuidAndLive = new ConcurrentHashMap<>();
    // 用户和渠道的映射
    public static ConcurrentHashMap<String, Channel> uuidAndChannel = new ConcurrentHashMap<String, Channel>();
    //渠道和用户的映射
    private static ConcurrentHashMap<String, String> idAndUuid = new ConcurrentHashMap<String, String>();

    private static final SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private RedisComponent redisComponent;


    // netty 客户端发消息的时候会被这个方法进行 监听
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {

        if (msg instanceof WebSocketFrame) {
            //正常的TEXT消息类型
            WebSocketFrame msg1 = (WebSocketFrame) msg;
            // ping请求
            if (msg1 instanceof CloseWebSocketFrame) {
                ctx.channel().close();
                return;
            }
            // ping请求
            if (msg1 instanceof PingWebSocketFrame) {
                ctx.channel().writeAndFlush(new PongWebSocketFrame(msg1.content().retain()));
                return;
            }
            // 二进制数据
            if (msg1 instanceof BinaryWebSocketFrame) {
                BinaryWebSocketFrame msg11 = (BinaryWebSocketFrame) msg1;
                ctx.channel().writeAndFlush(msg11);
                return;
            }
            // 处理客户端的消息
            handlerTextFrame(ctx, msg);
//            TextWebSocketFrame frame1 = (TextWebSocketFrame) msg;
//            ctx.channel().writeAndFlush(ctx.channel().remoteAddress() + ":" + frame1.text());
//            channels.writeAndFlush(ctx.channel().remoteAddress() + ":" + frame1.text());
//            log.info(ctx.channel().remoteAddress().toString() + "推送消息:{}",);

        }


    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        channels.add(ctx.channel());

    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        closeConnect(ctx);
        log.info("用户离开直播服务器,人数{}", channels.size());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        channels.add(ctx.channel());
        log.info("用户连接直播服务器,人数{}", channels.size());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//        closeConnect(ctx);

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        super.channelReadComplete(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            // websoket 握手成功之后 会执行 userEventTriggered 传入 evt 的类型为  WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE
        }
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateHandler = (IdleStateEvent) evt;
            switch (idleStateHandler.state()) {
                case ALL_IDLE:
                    closeConnect(ctx);
                    ;
                    break;
                case READER_IDLE:
                    // 在指定时间内读取不到客户端的消息  关闭通道
                    closeConnect(ctx);
//                    log.info("读取超时");
//                    System.out.println("读取超时");
                    break;
                case WRITER_IDLE:
                    // 在指定时间内 没有向客户端写入消息  发送 心跳 ping
//                    ctx.channel().writeAndFlush(new TextWebSocketFrame(buildModel("ping", MessageTypeEnum.HEART.getType(), MessageHandler.s.format(new Date())).toString()));
                    ;
                    break;
                default:
                    ;

            }

        }
        ctx.fireUserEventTriggered(evt);
//        super.userEventTriggered(ctx, evt);
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        log.error("出现异常了:");
//        ctx.close();
        closeConnect(ctx);
    }

    // 处理消息类型
    private void handlerTextFrame(ChannelHandlerContext ctx, Object msg) {
        TextWebSocketFrame frame1 = (TextWebSocketFrame) msg;
        MessageModel messageModel = JSONObject.parseObject(frame1.text(), MessageModel.class);
        messageModel.setTime(s.format(new Date()));
        String type = messageModel.getType();
        MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByType(type);
        Channel fromChannel = ctx.channel();

        if (messageTypeEnum == null) {
//            throw new ChatException("不支持的消息类型");
            fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "The message type is not supported", MessageTypeEnum.EXCEPTION.getType(), false).toString()));
            return;
        }
        if (TextUtil.isEmpty(messageModel.getMessageId())) {
//            throw new ChatException("不支持的消息类型");
            fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "The message id is null", MessageTypeEnum.EXCEPTION.getType(), false).toString()));
            return;
        }
        switch (messageTypeEnum) {
//            case HEART:
////                log.info("心跳:{}",messageModel.toString());
//                heartMessage(messageModel, fromChannel);
//                break;
            case BIND:
                bindMessage(messageModel, fromChannel);
                break;

            case INTOLIVE:
                // 进入直播间
                intoLive(messageModel, fromChannel);
                break;
            case LEAVELIVE:
                // 进入直播间
                leaveLive(messageModel, fromChannel);
                break;
            default:

                break;
        }
    }


    private void heartMessage(MessageModel messageModel, Channel fromChannel) {
        //客户端心跳消息
        Object offLineNotice = redisComponent.getValue(RedisKeyEnum.off_line_notice.getName() + messageModel.getFromUuid());
        if (!TextUtil.isEmpty(offLineNotice)) {
            MessageModel offLine = new MessageModel();
            offLine.setType(MessageTypeEnum.OFFLINETIP.getType());
            offLine.setOffLineTipMap(JSONObject.parseObject(offLineNotice.toString(), Map.class));
            fromChannel.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(offLine)));
        } else {
            fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "pong", MessageTypeEnum.HEART.getType(), true).toString()));
        }
    }


    private void bindMessage(MessageModel messageModel, Channel fromChannel) {
        // 用户第一次连接 建立绑定关系
        String fromUuid = messageModel.getFromUuid();
        if (TextUtil.isEmpty(fromUuid)) {
            fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "The message FromUuid is null", MessageTypeEnum.BIND.getType(), false).toString()));
            return;
        }
        uuidAndChannel.put(messageModel.getFromUuid(), fromChannel);
        idAndUuid.put(fromChannel.id().asLongText().replace("-", ""), messageModel.getFromUuid());

        fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "连接成功", MessageTypeEnum.BIND.getType(), true).toString()));
    }


    private void intoLive(MessageModel messageModel, Channel fromChannel) {
        String liveRoomId = messageModel.getLiveRoomId();

        if (TextUtil.isEmpty(liveRoomId)) {
            fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "The message liveRoomId is null", MessageTypeEnum.INTOLIVE.getType(), false).toString()));
            return;
        }
        ChannelGroup channelGroup = liveAndUuidChannel.get(liveRoomId);
        if (channelGroup == null) {
            channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            channelGroup.add(fromChannel);
            liveAndUuidChannel.put(liveRoomId, channelGroup);
        } else {
            channelGroup.add(fromChannel);
        }
        messageModel.setLiveNum(channelGroup.size());
        messageModel.setIsSuccess(true);
        // 添加映射关系  离开直播间和连接断开的时候方便直接找到对应的直播间删除
        uuidAndLive.put(messageModel.getFromUuid(), liveRoomId);
        channelGroup.writeAndFlush(new TextWebSocketFrame(messageModel.toString()));
//        fromChannel.writeAndFlush(new TextWebSocketFrame(messageModel.toString()));
    }

    private void leaveLive(MessageModel messageModel, Channel fromChannel) {
        String liveRoomId = messageModel.getLiveRoomId();
        if (TextUtil.isEmpty(liveRoomId)) {
            fromChannel.writeAndFlush(new TextWebSocketFrame(buildModel(messageModel.getMessageId(), "The message liveRoomId is null", MessageTypeEnum.LEAVELIVE.getType(), false).toString()));
            return;
        }
        ChannelGroup channelGroup = liveAndUuidChannel.get(liveRoomId);
        if (channelGroup != null) {
            channelGroup.remove(fromChannel);
            uuidAndLive.remove(messageModel.getLiveRoomId());
            if (channelGroup.size() == 0) {
                liveAndUuidChannel.remove(liveRoomId);
            }
            messageModel.setType(MessageTypeEnum.LIVENUM.getType());
            messageModel.setLiveNum(channelGroup.size());
            messageModel.setIsSuccess(true);
            channelGroup.writeAndFlush(new TextWebSocketFrame(messageModel.toString()));
        }
    }


    // 发送弹幕
    public boolean liveChat(MessageModel messageModel) {
        String liveRoomId = messageModel.getLiveRoomId();

        ChannelGroup channelGroup = liveAndUuidChannel.get(liveRoomId);
        if (channelGroup != null) {
            channelGroup.writeAndFlush(new TextWebSocketFrame(messageModel.toString()));
        } else {
            return false;
        }
        return true;
    }


    // 构造消息实体
    public MessageModel buildModel(String messageId, String content, String type, boolean isSuccess) {
        MessageModel messageModel = new MessageModel();
        messageModel.setContent(content);
        messageModel.setType(type);
        messageModel.setTime(s.format(new Date()));
        messageModel.setMessageId(messageId);
        messageModel.setIsSuccess(isSuccess);
        return messageModel;
    }

    // 用户断线离线 删除关联  修改下线状态
    private void closeConnect(ChannelHandlerContext ctx) {
        Channel fromChannel = ctx.channel();
        channels.remove(fromChannel);
        // 设置用户离线
        String fromUuid = idAndUuid.get(fromChannel.id().asLongText().replace("-", ""));
        if (!TextUtil.isEmpty(fromUuid)) {
            String liveRoomUuid = uuidAndLive.get(fromUuid);
            //当前用户是否存在进入的直播间 存在则移除
            if (!TextUtil.isEmpty(liveRoomUuid)) {
                ChannelGroup channelGroup = liveAndUuidChannel.get(liveRoomUuid);
                if (channelGroup != null) {
                    channelGroup.remove(fromChannel);
                    uuidAndLive.remove(fromUuid);
                    if (channelGroup.size() == 0) {
                        liveAndUuidChannel.remove(liveRoomUuid);
                    }
                }
            }
            // 关闭用户连接通道
            idAndUuid.remove(fromChannel.id().asLongText().replace("-", ""));
            uuidAndChannel.remove(fromUuid);
        }
        // 关闭通道
        fromChannel.close();
    }

    // 获取直播间人数
    public Integer getLiveRoomNum(String roomId) {
        ChannelGroup channels = liveAndUuidChannel.get(roomId);
        System.out.println(liveAndUuidChannel);
        return channels == null ? 0 : channels.size();
    }

    public void qaNotice(String liveRoomId, String info) {
        ChannelGroup channels = liveAndUuidChannel.get(liveRoomId);

        if (channels != null) {
            channels.writeAndFlush(new TextWebSocketFrame(buildModel("", info, MessageTypeEnum.QA_NOTICE.getType(), true).toString()));
        }

    }

    public void closeLive(String liveRoomId) {
        ChannelGroup channels = liveAndUuidChannel.get(liveRoomId);

        if (channels != null) {
            channels.writeAndFlush(new TextWebSocketFrame(buildModel("", "", MessageTypeEnum.CLOSE_LIVE.getType(), true).toString()));
        }

    }
}
